package com.autumn.repo.service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;

import com.autumn.platform.core.Utils;
import com.autumn.platform.core.exception.Throws;
import com.autumn.repo.Repo;
import com.autumn.repo.RepoConsts;
import com.autumn.repo.RepoUser;
import com.autumn.repo.RepoVersion;
import com.autumn.repo.RepoVersionFile;
import com.autumn.repo.meta.RepoUserImpl;
import com.autumn.repo.meta.RepoVersionFileImpl;
import com.autumn.repo.meta.RepoVersionImpl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

public class GitRepoService extends AbstractRepoService {

    private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private Cache<String, RepoVersion> cache = CacheBuilder.newBuilder()//新实例
            .expireAfterAccess(1, TimeUnit.HOURS)//本地缓存时间
            .maximumSize(1000L)
            .build();

    private Comparator<RepoVersionFile> repoVersionFileComparator = new Comparator<RepoVersionFile>() {
        @Override
        public int compare(RepoVersionFile o1, RepoVersionFile o2) {
            if (o1.isLeaf()) {
                if (o2.isLeaf()) {
                    return o1.getName().compareTo(o2.getName());
                } else {
                    return 1;
                }
            } else {
                if (o2.isLeaf()) {
                    return -1;
                } else {
                    return o1.getName().compareTo(o2.getName());
                }
            }
        }
    };

    @Override
    protected File[] filterRepoPath(File root) {
        return root.listFiles((File pathname) -> {//过滤包含“.git”子目录的目录
            String[] names = pathname.list((File dir, String name) -> {
                return ".git".equals(name);
            });
            return null != names && names.length >= 1;
        });
    }

    @Override
    public Repo create(RepoUser user, String name) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Repo delete(RepoUser user, String name) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public InputStream getInputStream(String name, String objectId) {
        try (Repository repo = getGitRepository(name)) {
            ObjectId id = null;
            if (Utils.isBlank(objectId)) {
                Ref ref = repo.getRefDatabase().getRef(Constants.HEAD);
                id = ref.getObjectId();
            } else {
                id = ObjectId.fromString(objectId);
            }

            ObjectLoader loader = repo.getObjectDatabase().open(id);
            return loader.openStream();
        } catch (IOException e) {
            throw Throws.createException(RepoConsts._600001, e, name);
        }
    }

    @Override
    public List<RepoVersion> listVersions(String name) {
        List<RepoVersion> versions = new ArrayList<>();
        try (Repository repo = getGitRepository(name); RevWalk revWalk = new RevWalk(repo)) {
            Ref ref = repo.getRefDatabase().getRef(Constants.HEAD);
            RevCommit commit = revWalk.parseCommit(ref.getObjectId());
            revWalk.markStart(commit);
            revWalk.forEach(c -> versions.add(convertRevCommit2Version(c)));
        } catch (IOException e) {
            throw Throws.createException(RepoConsts._600001, e, name);
        }
        return versions;
    }

    @Override
    public List<RepoVersionFile> listVersionFiles(String name, String version, String parent) {
        List<RepoVersionFile> files = null;
        try (Repository repo = getGitRepository(name); TreeWalk treeWalk = new TreeWalk(repo); RevWalk revWalk = new RevWalk(repo)) {
            if (RepoVersion.WORKSPACE.equals(version)) {//当前工作空间
                files = listWorkspaceFiles0(repo, treeWalk, revWalk, parent);
            } else {//某一个已提交的版本
                files = listVersionFiles0(repo, treeWalk, revWalk, version, parent);
            }
        } catch (Exception e) {
            throw Throws.createException(RepoConsts._600001, e, name);
        }
        Collections.sort(files, repoVersionFileComparator);
        return files;
    }

    @Override
    public RepoVersion checkoutVersion(String name, String version) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public RepoVersion submitVersion(RepoUser user, String commit) {
        // TODO Auto-generated method stub
        return null;
    }

    private List<RepoVersionFile> listWorkspaceFiles0(Repository repo, TreeWalk treeWalk, RevWalk revWalk, String parent) throws Exception {
        List<RepoVersionFile> files;
        List<String> names = new ArrayList<>();
        if (Utils.isBlank(parent) || ObjectId.isId(parent)) {
            Ref ref = repo.getRefDatabase().getRef(Constants.HEAD);
            files = this.listVersionFiles0(repo, treeWalk, revWalk, ref.getObjectId().getName(), parent);
            for (RepoVersionFile file : files) {
                names.add(file.getName());
            }
            if (!Utils.isBlank(parent)) {
                treeWalk.reset();
                RevCommit commit = revWalk.parseCommit(ref.getObjectId());
                treeWalk.addTree(commit.getTree());
                treeWalk.setRecursive(false);
                parent = this.findParentPath(treeWalk, parent);
            }
        } else {
            files = new ArrayList<>();
        }

        File parentFile = repo.getWorkTree();
        if (!Utils.isBlank(parent)) {
            Path path = Paths.get(parentFile.getPath(), parent.split("[/||\\\\]"));
            parentFile = path.toFile();
            if (!parent.endsWith("/")) {
                parent = parent + "/";
            }
        } else {
            parent = "";
        }

        for (File f : parentFile.listFiles()) {
            if (!names.contains(f.getName())) {
                RepoVersionFile versionFile = this.convert2RepoVersionFile(f, parent);
                files.add(versionFile);
            }
        }
        return files;
    }

    private List<RepoVersionFile> listVersionFiles0(Repository repo, TreeWalk treeWalk, RevWalk revWalk, String version, String parent) throws Exception {
        Git git = Git.wrap(repo);
        List<RepoVersionFile> files = new ArrayList<>();
        RevCommit commit = revWalk.parseCommit(ObjectId.fromString(version));
        RevTree tree = commit.getTree();
        // 先查找parent的path
        String parentPath = "";
        if (!Utils.isBlank(parent)) {
            treeWalk.addTree(tree);
            treeWalk.setRecursive(false);
            parentPath = this.findParentPath(treeWalk, parent);
            tree = revWalk.parseTree(ObjectId.fromString(parent));
            treeWalk.reset();
        }

        treeWalk.addTree(tree);
        treeWalk.setRecursive(false);
        while (treeWalk.next()) {
            RepoVersionFile file = convert2RepoVersionFile(git, repo, treeWalk, parentPath);
            files.add(file);
        }
        return files;
    }

    private String findParentPath(TreeWalk treeWalk, String parent) throws Exception {
        if (!Utils.isBlank(parent)) {
            while (treeWalk.next()) {
                if (parent.equals(treeWalk.getObjectId(0).name())) {
                    return treeWalk.getPathString() + "/";
                } else if (treeWalk.isSubtree()) {
                    treeWalk.enterSubtree();
                }
            }
        }
        return "";
    }

    private RepoVersionFile convert2RepoVersionFile(Git git, Repository repo, TreeWalk treeWalk, String parentPath) throws Exception {
        RepoVersionFileImpl file = new RepoVersionFileImpl();
        ObjectId objectId = treeWalk.getObjectId(0);

        file.setId(objectId.name());
        file.setSize(repo.open(objectId).getSize());
        file.setName(treeWalk.getPathString());
        file.setPath(parentPath + file.getName());
        file.setLeaf(!treeWalk.isSubtree());
        RevCommit fileCommit = git.log().addPath(file.getPath()).call().iterator().next();
        RepoVersion repoVersion = this.convertRevCommit2Version(fileCommit);
        file.setVersion(repoVersion);
        return file;
    }

    private RepoVersionFile convert2RepoVersionFile(File repo, String parentPath) throws Exception {
        RepoVersionFileImpl file = new RepoVersionFileImpl();

        file.setSize(repo.length());
        file.setName(repo.getName());
        file.setPath(parentPath + repo.getName());
        file.setLeaf(repo.isFile());
        return file;
    }

    /**
     * @param c
     * @return
     */
    private RepoVersion convertRevCommit2Version(RevCommit c) {
        String name = c.getName();
        RepoVersion version = this.cache.getIfPresent(name);
        if (null == version) {
            synchronized (this.cache) {
                version = this.cache.getIfPresent(name);
                if (null == version) {
                    RepoVersionImpl versionImpl = new RepoVersionImpl();

                    versionImpl.setVersion(c.getName());
                    versionImpl.setCommit(c.getFullMessage());
                    versionImpl.setTimestamp(c.getAuthorIdent().getWhen().getTime());

                    RepoUserImpl author = new RepoUserImpl();
                    author.setName(c.getAuthorIdent().getName());
                    author.setEmail(c.getAuthorIdent().getEmailAddress());
                    versionImpl.setAuthor(author);

                    String description = new StringBuilder()
                            .append("[").append(dateFormat.format(c.getAuthorIdent().getWhen())).append("]")
                            .append("[").append(versionImpl.getAuthor().getName()).append("]")
                            .append(versionImpl.getCommit())
                            .toString();
                    versionImpl.setDescription(description);

                    version = versionImpl;
                    this.cache.put(name, version);
                }
            }
        }
        return version;
    }

    private Repository getGitRepository(String name) {
        try {
            String path = super.getRoot() + "/" + name;
            Git git = Git.open(new File(path));
            return git.getRepository();
        } catch (Exception e) {
            throw Throws.createException(RepoConsts._600001, e, name);
        }
    }
}
